package com.example.leetcode.design.listener.basic;

import org.assertj.core.util.Preconditions;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

public class ObserverRegistry {


    private ConcurrentHashMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>();


    public void register(Object observer){
        Map<Class<?>, Collection<ObserverAction>> allObserverAction = findAllObserverAction(observer);
        for (Map.Entry<Class<?>,Collection<ObserverAction>> entry:allObserverAction.entrySet()) {
            Collection<ObserverAction> value = entry.getValue();
            Class<?> key = entry.getKey();
            CopyOnWriteArraySet<ObserverAction> observerActions = registry.get(key);
            if (observerActions == null){
                registry.putIfAbsent(key,new CopyOnWriteArraySet<>());
                observerActions = registry.get(key);
            }
            observerActions.addAll(value);
        }
    }


    public Map<Class<?>, Collection<ObserverAction>> findAllObserverAction(Object observer){
        Map<Class<?>,Collection<ObserverAction>> observerActions = new HashMap<>();
        Class<?> clazz = observer.getClass();
        List<Method> annotatedMethods = getAnnotatedMethods(clazz);
        for (Method method:annotatedMethods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            Class<?> parameterType = parameterTypes[0];
            if (!observerActions.containsKey(parameterType)){
                observerActions.put(parameterType,new ArrayList<>());
            }
            observerActions.get(parameterType).add(new ObserverAction(observer,method));
        }
        return observerActions;
    }


    public List<ObserverAction> getMatchedObserverActions(Object event){
        List<ObserverAction> observerActions = new ArrayList<>();
        Class<?> aClass = event.getClass();
        for (Map.Entry<Class<?>,CopyOnWriteArraySet<ObserverAction>> entry:registry.entrySet()) {
            Class<?> key = entry.getKey() ;
            CopyOnWriteArraySet<ObserverAction> value = entry.getValue();
            if (key.isAssignableFrom(aClass)){
                observerActions.addAll(value);
            }
        }
        return observerActions;
    }


    private List<Method> getAnnotatedMethods(Class<?> clazz){
        List<Method> annotatedMethods = new ArrayList<>();
        for (Method method:clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Subscribe.class)){
                Class<?>[] parameterTypes = method.getParameterTypes();
                Preconditions.checkArgument(parameterTypes.length == 1,"method parameter one");
                annotatedMethods.add(method);
            }
        }
        return annotatedMethods;
    }
}
